#import "SolarSeekConnection.h"
#include <openssl/md5.h>

@implementation SolarSeekConnection
@synthesize input;
@synthesize output;

-(SolarSeekStore *)store {
  return [[[NSApplication sharedApplication] delegate] store];
}

-(id) init {
	if (self = [super init]) {
    input = [[SolarSeekMessageReader alloc] init];    
    output = [[SolarSeekMessageWriter alloc] init];
    
    started = NO;
    totalTickets = 1000;
    
		input.delegate = self;
		output.delegate = self;
	}
	
	return self;		
}

-(void) start {
  if (input.stream == nil || output.stream == nil) 
    [self performSelectorInBackground:@selector(connect:) withObject:[NSThread currentThread]];
  else 		
    [self openStreams:nil];
}

-(void) openStreams:(id)unused {
  started = YES;
  // add objects to runloop
  [self.input open];
  [self.output open];
}

-(void) connect:(id)thread {
  NSInputStream *inputStream = nil;
  NSOutputStream *outputStream = nil;
  
  NSHost *host = [NSHost hostWithAddress:@"127.0.0.1"]; // TODO: Fix me 
  NSInteger port = 2240;
  
  // this could take a lot of time ...
  [NSStream getStreamsToHost:host port:port inputStream:&inputStream outputStream:&outputStream];
  
  self.input.autoOpenStream = NO;
  self.output.autoOpenStream = NO;
  
  self.input.stream = inputStream;
  self.output.stream = outputStream;
  
  [self performSelector:@selector(openStreams:) onThread:thread withObject:nil waitUntilDone:NO];
}

-(void) closeStreams {
  input.stream = nil;
  output.stream = nil;
}

// called after a message is sent 
-(void) messageSent:(MuseekMessage *)message {
}

// called when something is wrong with the streams
-(void) disconnect {
  [self closeStreams];
}

-(void) internalSend:(MuseekMessage *)message {
  [output send:message];
}

-(void) send:(MuseekMessage *) message {
  [self performSelectorInBackground:@selector(internalSend:) withObject:message];
}

-(NSString *) md5hash:(NSString *)toHash {
  NSData *data = [toHash dataUsingEncoding:NSUTF8StringEncoding];
  if (data) {
    NSMutableData *digestData = [NSMutableData dataWithLength:MD5_DIGEST_LENGTH];
    if (digestData && MD5([data bytes], [data length], [digestData mutableBytes])) {
      unsigned char* digest = (unsigned char*)[digestData bytes];
      NSString* s = [NSString stringWithFormat: @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
                     digest[0], digest[1], 
                     digest[2], digest[3],
                     digest[4], digest[5],
                     digest[6], digest[7],
                     digest[8], digest[9],
                     digest[10], digest[11],
                     digest[12], digest[13],
                     digest[14], digest[15]];
      return s;
    }
  }
  return nil;
}

-(void) search:(NSString *)term {
  NSLog(@"Searching...");
  MuseekMessage *newMessage = [MuseekMessage messageWithData:[NSArray arrayWithObjects:[NSNumber numberWithInt:0x401], [NSNumber numberWithInt:0], term, nil] order:@"iis"];
  [self send:newMessage];
  
}

-(void) handleType0001:(MuseekMessage *)message {
  UInt32 version = [message readUInt32];
  NSString *challenge = [message readString];
  
  NSString *digest = [self md5hash:[NSString stringWithFormat:@"%@%@", challenge, @"solarseek"]];
  
  MuseekMessage *newMessage = [MuseekMessage messageWithData:[NSArray arrayWithObjects:[NSNumber numberWithInt:2], @"MD5", digest, [NSNumber numberWithInt:15], nil] order:@"issi"];
  [self send:newMessage];
}

-(void) handleType0002:(MuseekMessage *)message {
  Boolean ok = ([message readByte] == 0)?NO:YES;
  if (ok)
    NSLog(@"Login successful");
  else
    NSLog(@"Login failed. Reason : %@", [message readString]);
    
}

-(void) handleType0401:(MuseekMessage *)message {
  NSString *query = [message readString];
  UInt32 ticket = [message readUInt32];
  
  Ticket *object = [self.store new:@"Ticket"];
  
  object.number = [NSNumber numberWithInt:ticket];
  object.term = query;
  
  // now adding new item into the sidebar
  [self.store newSidebarItemWithName:query parent:[self.store find:@"SidebarItem" withPredicate:[NSPredicate predicateWithFormat:@"parent == nil && type == %@", [NSNumber numberWithInt:searchType]]] tag:ticket type:searchType order:totalTickets++ andIsHidden:NO];
  
  [self.store.managedObjectContext save:nil];
}

-(void) downloadFile:(Result *)file {
  NSString *filename = file.uri;
  NSString *usernme = file.user.name;
  NSNumber* filesize = file.size;
  
  MuseekMessage *newMessage = [MuseekMessage messageWithData:[NSArray arrayWithObjects:[NSNumber numberWithInt:0x503], usernme, filename, filesize, nil] order:@"issl"];
  [self send:newMessage];
}

-(void) handleType0402:(MuseekMessage *)message {
  UInt32 ticketNumber = [message readUInt32];
  
  Ticket *ticket = [self.store findTicketWithTicketNumber:ticketNumber];
  
  NSString *username = [message readString];

  User* user = (User *)[self.store new:@"User"];
  
  Boolean hasFreeSlots = [message readByte] == 0?NO:YES;
  UInt32 averageSpeed = [message readUInt32];
  UInt32 queueLength = [message readUInt32];
  
  user.averageSpeed = [NSNumber numberWithInt:averageSpeed];
  user.hasFreeSlots = [NSNumber numberWithBool:hasFreeSlots];
  user.name = username;
  user.queueLength = [NSNumber numberWithInt:queueLength];
  
  UInt32 files = [message readUInt32];
  NSMutableSet *lotOfFiles = [[NSMutableSet alloc] initWithCapacity:files];
  
  for (UInt32 i = 0; i < files; i++) {
    NSString *filename = [message readString];
    UInt64 size = [message readUInt64];
    NSString *extended = [message readString];
    UInt32 attributes = [message readUInt32];
    
    UInt32 bitrate = -1;
    UInt32 length = -1;
    UInt32 vbr = -1;
    
    if (attributes >= 1)
      bitrate = [message readUInt32];
    
    if (attributes >= 2)
      length = [message readUInt32];
    
    if (attributes >= 3)
      vbr = [message readUInt32];
    
    if (attributes > 3) {
      NSLog(@"Ouch, number of attributes is greater than 3, ignoring. Please tell the developers!");
      
      for (UInt32 j = 3; j < attributes; j++) 
        [message readUInt32];
      
    }
    
    //NSLog(@"%05d: [%@] -> %@", ticketNumber, username, filename); 
    
    Result *file = [self.store new:@"Result"];
    file.uri = filename;
    file.filename = filename;
    file.size = [NSNumber numberWithLong: size];
    
    [lotOfFiles addObject:file];
  }
  
  [user addFiles:lotOfFiles];
  [ticket addFiles:lotOfFiles];
  
  if ([ticket.files count] > 1000) { // TODO: move to a preference or at least to a .plist. 
    // stop the search.
    MuseekMessage *newMessage = [MuseekMessage messageWithData:[NSArray arrayWithObjects:[NSNumber numberWithInt:0x402], ticket.number, nil] order:@"ii"];
    [self send:newMessage];
  }
  
  NSError *error = nil;
  [self.store save];
}

-(Transfer *) newTransferFromMessage:(MuseekMessage *)message {
  Boolean isUpload = [message readByte] == 0 ? NO : YES;
  NSString *user = [message readString];
  NSString *path = [message readString];
  
  
}

-(void) handleType0500:(MuseekMessage *)message {
}


-(void) handleType0501:(MuseekMessage *)message {
}

// called when a new message is received
-(void) handleMessage:(MuseekMessage *)message {
  int code = [message.code intValue];
  
  [message setPos:sizeof(uint32_t)];// after the code.
  
  switch (code) {
    case 0x0001:
      [self handleType0001:message];
      break;
    case 0x0002:
      [self handleType0002:message];
      break;
    case 0x0401:
      [self handleType0401:message];
      break;
    case 0x0402:
      [self handleType0402:message];
      break;
    default:
      break;
  }
  /*
   NSString *selectorName = [NSString stringWithFormat:@"handleType%04X:", code];
   SEL selector = NSSelectorFromString(selectorName);
   if ([self respondsToSelector:selector]) {
   [self performSelector:selector withObject:message];
   } else 
   NSLog(@"Skipping museekd message 0x%04X...", code);
   */
}


@end
